var monthList = new Array(
	'January','February','March','April','May','June',
	'July','August','September','October','November','December');
var sunType = { Sunrise:0, Sunset:1 };
	

function radToDeg(angleRad) 
/***********************************************************************/
{
	debug( 'radToDeg in : ' + ['angleRad', angleRad].join(', '));
	var deg = (180.0 * angleRad / Math.PI);
	debug( 'radToDeg out: ' + ['deg', deg].join(', '));
	return deg;
}

function degToRad(angleDeg) 
/*********************************************************************/
{
	debug( 'degToRad in : ' + ['angleDeg', angleDeg].join(', '));
	var rad = (Math.PI * angleDeg / 180.0);
	debug( 'degToRad out: ' + ['rad', rad].join(', '));
	return rad;
}

function calcTimeJulianCent(JD)
/*********************************************************************/
{
	debug( 'calcTimeJulianCent in : ' + ['JD', JD].join(', '));
	var T = (JD - 2451545.0)/36525.0;
	debug( 'calcTimeJulianCent out: ' + ['T', T].join(', '));
	return T;
}

function calcDayFromJD(JD)
/***********************************************************************
 * Purpose: Calendar day (minus year) from Julian Day
 * Arguments:
 *   JD   : Julian Day
 * Return value:  String date in the form DD-MONTH
 ***********************************************************************/
{
	debug( 'calcDayFromJD in : ' + ['JD', JD].join(', '));
	var z = Math.floor(JD + 0.5);
	var f = (JD + 0.5) - z;
	if (z < 2299161) {
		var A = z;
	} else {
		alpha = Math.floor((z - 1867216.25)/36524.25);
		var A = z + 1 + alpha - Math.floor(alpha/4);
	}

	var B = A + 1524;
	var C = Math.floor((B - 122.1)/365.25);
	var D = Math.floor(365.25 * C);
	var E = Math.floor((B - D)/30.6001);

	var day = B - D - Math.floor(30.6001 * E) + f;
	var month = (E < 14) ? E - 1 : E - 13;
	var year = (month > 2) ? C - 4716 : C - 4715;
	
	var dateStr = ((day<10 ? "0" : "") + day + ' ' + monthList[month-1]);
	debug( 'calcDayFromJD out: ' + ['dateStr', dateStr].join(', '));
	return dateStr;
}

function calcGeomMeanLongSun(t)
/*********************************************************************/	
{
	debug( 'calcGeomMeanLongSun in : ' + ['t', t].join(', '));
	var L0 = 280.46646 + t * (36000.76983 + 0.0003032 * t);
	while(L0 > 360.0)
	{
		L0 -= 360.0;
	}
	while(L0 < 0.0)
	{
		L0 += 360.0;
	}
	debug( 'calcGeomMeanLongSun out: ' + ['L0', L0].join(', '));
	return L0;		// in degrees
}

/***********************************************************************
 * t : number of Julian centuries since J2000.0
 * Return value - the Geometric Mean Anomaly of the Sun in degrees
 ***********************************************************************/
function calcGeomMeanAnomalySun(t)
{
	debug( 'calcGeomMeanAnomalySun in : ' + ['t', t].join(', '));
	var M1 = 357.52911 + t * (35999.05029 - 0.0001537 * t);
	var M2 = (357.52911 + (35999.05029 * t) - (0.0001537 * t * t) );
	
	debug( 'calcGeomMeanAnomalySun out: ' + ['M1', M1, 'M2', M2].join(', '));
	return M1;		// in degrees
}
	
/***********************************************************************
 * t : number of Julian centuries since J2000.0
 * Return value: the unitless eccentricity	
 ***********************************************************************/
function calcEccentricityEarthOrbit(t)
{
	debug( 'calcEccentricityEarthOrbit in : ' + ['t', t].join(', '));
	var e = 0.016708634 - t * (0.000042037 + 0.0000001267 * t);
	debug( 'calcEccentricityEarthOrbit out: ' + ['e', e].join(', '));
	return e;		// unitless
}

function calcJDFromJulianCent(t)
/***********************************************************************
 * Purpose: convert centuries since J2000.0 to Julian Day.
 * Arguments:
 *   t : number of Julian centuries since J2000.0
 * Return value:
 *   the Julian Day corresponding to the t value
 ***********************************************************************/
{
	debug( 'calcJDFromJulianCent in : ' + ['t', t].join(', '));
	var JD = t * 36525.0 + 2451545.0;
	debug( 'calcJDFromJulianCent out: ' + ['JD', JD].join(', '));
	return JD;
}

function calcMeanObliquityOfEcliptic(t)
/***********************************************************************		
 * Purpose: calculate the mean obliquity of the ecliptic
 * Arguments:	
 *   t : number of Julian centuries since J2000.0
 * Return value:	
 *   mean obliquity in degrees
 ***********************************************************************/
{
	debug( 'calcMeanObliquityOfEcliptic in : ' + ['t', t].join(', '));
	var seconds = 21.448 - t*(46.8150 + t*(0.00059 - t*(0.001813)));
	var e0 = 23.0 + (26.0 + (seconds/60.0))/60.0;
	debug( 'calcMeanObliquityOfEcliptic out: ' + ['e0', e0].join(', '));
	return e0;		// in degrees
}
	
function calcObliquityCorrection(t)
/*********************************************************************/
{
	debug( 'calcObliquityCorrection in : ' + ['t', t].join(', '));
	var e0 = calcMeanObliquityOfEcliptic(t);
	var omega = 125.04 - 1934.136 * t;
	var e = e0 + 0.00256 * Math.cos(degToRad(omega));
	debug( 'calcObliquityCorrection out: ' + ['e', e].join(', '));
	return e;		// in degrees
}

function calcEquationOfTime(t)
/*********************************************************************
 * Equation of Time - The difference between the apparent time
 * and the mean time. The Equation of Time value for the date
 * is used to convert Sun Dial Time to Local Mean Time.
 */	
{
	debug( 'calcEquationOfTime in : ' + ['t', t].join(', ') );
	var epsilon = calcObliquityCorrection(t);
	var l0 = calcGeomMeanLongSun(t);
	var e = calcEccentricityEarthOrbit(t);
	var m = calcGeomMeanAnomalySun(t);
	var y = Math.tan(degToRad(epsilon)/2.0);
	y *= y;
	var sin2l0 = Math.sin(2.0 * degToRad(l0));
	var sinm   = Math.sin(degToRad(m));
	var cos2l0 = Math.cos(2.0 * degToRad(l0));
	var sin4l0 = Math.sin(4.0 * degToRad(l0));
	var sin2m  = Math.sin(2.0 * degToRad(m));
	var Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0
			- 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m;
	var eqOfTime = radToDeg(Etime)*4.0;	// in minutes of time
	debug( 'calcEquationOfTime out: ' + ['eqOfTime', eqOfTime].join(', ') );
	return eqOfTime;
}
	
function calcSunEqOfCenter(t)
/*********************************************************************/
{
	debug( 'calcSunEqOfCenter in : ' + ['t', t].join(', ') );
	var m = calcGeomMeanAnomalySun(t);
	var mrad = degToRad(m);
	var sinm = Math.sin(mrad);
	var sin2m = Math.sin(mrad+mrad);
	var sin3m = Math.sin(mrad+mrad+mrad);
	var C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t))
				+ sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289;
	debug( 'calcSunEqOfCenter out: ' + ['C', C].join(', ') );
	return C;		// in degrees
}
	
function calcSunTrueLong(t)
/*********************************************************************/
{
	debug( 'calcSunTrueLong in : ' + ['t', t].join(', ') );
	var l0 = calcGeomMeanLongSun(t);
	var c = calcSunEqOfCenter(t);
	var O = l0 + c;
	debug( 'calcSunTrueLong out: ' + ['O', O].join(', ') );
	return O;		// in degrees
}

function calcSunApparentLong(t)
/***********************************************************************/
{
	debug( 'calcSunApparentLong in : ' + ['t', t].join(', ') );
	var o = calcSunTrueLong(t);
	var omega = 125.04 - 1934.136 * t;
	var lambda = o - 0.00569 - 0.00478 * Math.sin(degToRad(omega));
	debug( 'calcSunApparentLong out: ' + ['lambda', lambda].join(', ') );
	return lambda;		// in degrees
}

function calcSunDeclination(t)
/*********************************************************************/
{
	debug( 'calcSunDeclination in : ' + ['t', t].join(', ') );
	var e = calcObliquityCorrection(t);
	var lambda = calcSunApparentLong(t);
	var sint = Math.sin(degToRad(e)) * Math.sin(degToRad(lambda));
	var theta = radToDeg(Math.asin(sint));
	debug( 'calcSunDeclination out: ' + ['theta', theta].join(', ') );
	return theta;		// in degrees
}

function calcHourAngle(sunriseOrSunset, lat, solarDec)
/*********************************************************************/
{
	debug( 'calcHourAngle in : ' + ['sunriseOrSunset', sunriseOrSunset,
			'lat', lat, 'solarDec', solarDec].join(', ') );
	var latRad = degToRad(lat);
	var sdRad  = degToRad(solarDec)
	var HAarg = (Math.cos(degToRad(90.833))
					/ (Math.cos(latRad)*Math.cos(sdRad))
					- Math.tan(latRad) * Math.tan(sdRad));
	var HA = (Math.acos(Math.cos(degToRad(90.833))
					/ (Math.cos(latRad)*Math.cos(sdRad))
					- Math.tan(latRad) * Math.tan(sdRad)));
	var hourAngle;
	if( sunriseOrSunset == sunType.Sunrise ){
		hourAngle = -HA;		// in radians
	}else if( sunriseOrSunset == sunType.Sunset ){
		hourAngle = HA;		// in radians
	}
	debug( 'calcHourAngle out: ' + ['hourAngle', hourAngle].join(', ') );
	return hourAngle;
	}

function timeStringShortAMPM(minutes, JD)
/*********************************************************************/
{
	debug( 'timeStringShortAMPM in : ' + ['minutes', minutes, 'JD', JD].join(', '));
	var julianday = JD;
	var floatHour = minutes / 60;
	var hour = Math.floor(floatHour);
	var floatMinute = 60 * (floatHour - Math.floor(floatHour));
	var minute = Math.floor(floatMinute);
	var floatSec = 60 * (floatMinute - Math.floor(floatMinute));
	var second = Math.floor(floatSec);
	var PM = false;
	minute += (second >= 30)? 1 : 0;
	if (minute >= 60) 
	{
		minute -= 60;
		hour ++;
	}
	var daychange = false;
	if (hour > 23) 
	{
		hour -= 24;
		daychange = true;
		julianday += 1.0;
	}
	if (hour < 0)
	{
		hour += 24;
		daychange = true;
		julianday -= 1.0;
	}
	if (hour > 11)
	{
		hour -= 12;
		PM = true;
	}
	if (hour == 0)
	{
		PM = false;
		hour = 12;
	}
	var timeStr = hour + ":";
	if (minute < 10)	//	i.e. only one digit
		timeStr += "0" + minute + ((PM)?"PM":"AM");
	else
		timeStr += "" + minute + ((PM)?"PM":"AM");
	debug( 'timeStringShortAMPM out: ' + ['timeStr', timeStr].join(', '));
	return timeStr;
}
	
function calcTimeUTC(sunriseOrSunset, t, latitude, longitude)
/*********************************************************************
 * JD : julian day
 * latitude : latitude of observer in degrees
 * longitude : longitude of observer in degrees
 * Return value - time in minutes from zero Z
 ***********************************************************************/
{
	debug( 'calcTimeUTC in : ' + ['sunriseOrSunset', sunriseOrSunset, 't', t,
			'latitude', latitude, 'longitude', longitude].join(', '));
	var hourAngle;
	var eqTime = calcEquationOfTime(t);    
	var solarDec = calcSunDeclination(t);
	hourAngle = calcHourAngle(sunriseOrSunset, latitude, solarDec);
	var delta = longitude - radToDeg(hourAngle);
	var timeDiff = 4 * delta;	// in minutes of time
	var timeUTC = 720 + timeDiff - eqTime;	// in minutes
	debug( 'calcTimeUTC out: ' + ['delta', delta, 'timeDiff', timeDiff,
			'timeUTC', timeUTC].join(', '));
    return timeUTC;
}
	
function calcUTC(sunriseOrSunset, JD, latitude, longitude)
/*********************************************************************
 * JD : julian day
 * latitude : latitude of observer in degrees
 * longitude : longitude of observer in degrees
 * Return value - time in minutes from zero Z
 ***********************************************************************/
{
	debug( 'calcUTC in : ' + ['sunriseOrSunset', sunriseOrSunset, 'JD', JD,
			'latitude', latitude, 'longitude', longitude].join(', '));
	var oldT = calcTimeJulianCent(JD);
	var oldTimeUTC
		= calcTimeUTC(sunriseOrSunset, oldT, latitude, longitude);
	var newT
		= calcTimeJulianCent(calcJDFromJulianCent(oldT)
			+ parseFloat(oldTimeUTC/1440.0)); 
	var newTimeUTC = calcTimeUTC(sunriseOrSunset, newT, latitude, longitude);
	debug( 'calcUTC out: ' + ['newTimeUTC', newTimeUTC].join(', '));
    return newTimeUTC;
}

function calcJD(year, month, day)
/***********************************************************************
 * Purpose: Julian day from calendar day
 * Arguments:
 *   year : 4 digit year
 *   month: January = 1
 *   day  : 1 - 31
 * Return value: The Julian day corresponding to the date
 * Note:
 *   Number is returned for start of day.  Fractional days should be
 *   added later.
 ************************************************************************/
{
	debug('calcJD in : ' + ['year', year, 'month', month, 'day', day].join(', '));
	var year = parseInt( year );
	var month = parseInt( month );
	var day = parseInt( day );
	if (month <= 2)
	{
		year -= 1;
		month += 12;
	}
	var A = Math.floor(year/100);
	var B = 2 - A + Math.floor(A/4);
	var JD = Math.floor(365.25*(year + 4716))
				+ Math.floor(30.6001*(month+1)) + day + B - 1524.5;
	debug('calcJD out: ' + ['A', A, 'B', B, 'JD', JD].join(', '));
	return JD;
}

function getTimeStr( timeGMT, timezone, daySavings, JD )
{
	debug('getTimeStr in : ' + ['timeGMT', timeGMT, 'timezone', timezone,
			'daySavings', daySavings, 'JD', JD].join(', '));
	var timeStr = '';
	if( typeof(timeGMT) == 'number' ? true : false )
	{
		var timeLST = timeGMT - (60 * timezone) + daySavings;	
		debug( 'timeLST: ' + timeLST );
		timeStr = timeStringShortAMPM(timeLST, JD);
	}
	debug('getTimeStr out: ' + ['timeStr', timeStr].join(', '));
	return timeStr;
}

function calcSun(latitude, longitude, year, month, day)
{
	debug("=========================");
	debug('calcSun in : ' + ['latitude', latitude, 'longitude', longitude,
			'year', year, 'month', month, 'day', day].join(', '));
	var month = parseInt( month.replace(/^0/, '') );
	var day = parseInt( day.replace(/^0/, '') );
	var year = parseInt( year );
	var strDate = monthList[month-1] + ' ' + day + ', ' + year;
	var x = new Date( strDate );
	var timezone = Math.floor(( Math.abs(longitude) / 15 ));
	daySavings = isDayLightSavings( strDate );
	
	if((latitude >= -90) && (latitude < -89.8)) latitude = -89.8;
	if ((latitude <= 90) && (latitude > 89.8)) latitude = 89.8;
	var JD = (calcJD(year, month, day));
	var riseTimeGMT = calcUTC(sunType.Sunrise, JD, latitude, longitude);
	var riseStr = getTimeStr( riseTimeGMT, timezone, daySavings, JD );
	var setTimeGMT = calcUTC(sunType.Sunset, JD, latitude, longitude);
	var setStr = getTimeStr( setTimeGMT, timezone, daySavings, JD );
	debug('calcSun out: ' + ['riseStr', riseStr, 'setStr', setStr].join(', '));
	return [riseStr, setStr];
}

function isDayLightSavings(checkDate)
{
	var stTime = new Date("January 1, 2001");
	var dlTime = new Date("July 1, 2001");
	var ckTime = new Date(checkDate);
	var dlOffset = stTime.getTimezoneOffset() - dlTime.getTimezoneOffset();
	var thisOffset = ckTime.getTimezoneOffset() - stTime.getTimezoneOffset();
	debug( 'isDayLightSavings: ' + ckTime.toString() + ' - '
			+ Boolean( dlOffset * thisOffset ) );
	debug( 'date: ' + ckTime.toDateString() );
	return parseInt( Boolean( dlOffset * thisOffset ) + 0);
};
